/** @file    DiscoveryUdpImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: DiscoveryUdpImpl.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the DiscoveryUdpImpl class.
 * The header for this class can be found in DiscoveryUdpImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "DiscoveryUdpImpl.h"

 #include <framework/network/NetworkUtilities.h>
 #include <core/NetworkManager.h>
 #include <core/WoshKernel.h>
 #include <core/ObjectFactory.h>
 #include <core/Utilities.h>
 

using namespace std;
using namespace wosh;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

DiscoveryUdpImpl::DiscoveryUdpImpl( wosh::BundleGeneric& bundle )
	: wosh::BundleGenericWorker(bundle) {
	this->bindAddressPort = "";
	this->freqNotify = 0;
	this->sendNow = false;
	this->socket = NULL;

	// static (outgoing message) buffer, its' so small we can allocate here in consrtuctor
	this->msgBuffer = new char[DiscoveryUdpMessage::MaxLength];
	memset( this->msgBuffer, '\0', DiscoveryUdpMessage::MaxLength );
 }

DiscoveryUdpImpl::~DiscoveryUdpImpl() {
	Log(LOG_DEBUG, ":~DiscoveryUdpImpl() : Destroying.." );
	if ( isThreadRunning() ) {
		Log(LOG_WARNING, ":~DiscoveryUdpImpl() : Destroying while running, forcing stop.." );
		quitThread(10000);
	 }
	// free the outgoing message buffer
	delete [] this->msgBuffer; this->msgBuffer = NULL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

bool DiscoveryUdpImpl::initializingThread() {
	this->socket = ObjectFactory::createTypeOf<wosh::network::SocketUdp>(false);
	if ( this->socket == NULL ) {
		Log(LOG_CRITICAL, ":initializingThread() FAILED Creating SocketUdp!" );
		return false;
	 }
	Log(LOG_DEBUG, ":initializingThread() Created Socket of type %s", this->socket->getClassName() );
	// bind socket callbacks (incoming datagrams)
	this->socket->setListener(this);
	// bind the socket
	Log(LOG_VERBOSE, ":initializingThread() Binding Socket to %s", this->bindAddressPort.c_str() );
	bool set = this->socket->setAddressPort(this->bindAddressPort);
	if ( !set ) {
		Log(LOG_CRITICAL, ":initializingThread() FAILED Setting binding address [%s]", this->bindAddressPort.c_str() );
		delete this->socket; this->socket = NULL;
		return false; // initializing failed, thread won't start
	 }
	WRESULT binded = this->socket->bindSocket();
	if ( WFAILED(binded) ) {
		Log(LOG_CRITICAL, ":initializingThread() FAILED#%d Binding socket", binded );
		delete this->socket; this->socket = NULL;
		return false; // initializing failed, thread won't start
	 }
	Log(LOG_INFO, ":initializingThread() Binded to %s", this->bindAddressPort.c_str() );
	// get local-host object and update my own fieds (such as bindaddress)
	wosh::WoshHost* localHost = WoshKernel::getInstance()->network().getLocalWoshHost(); // it's a copy, I'll own it
	localHost->setDiscoverSource( this->bindAddressPort );
	localHost->setDiscoveryBindAddress( this->bindAddressPort );
	localHost->setNotifyFrequency( this->freqNotify );
	localHost->setDiscoveryProtocolVersion( this->msgBase.getProtocolVersion() );
	// forward updated object to NetworkManager
	WoshKernel::getInstance()->network().updateWoshHost( localHost );
	// done, free the temporary instance
	delete localHost; localHost = NULL;
	return true; // initializing done, let's move to runThread()
 }

void DiscoveryUdpImpl::runThread() {
	unsigned int counter = this->freqNotify -1; // start in one second
	int counter_int = 0;
	while( this->running ) {
		// setup a better time-accurancy, so we react faster to requests
		// such as send-now and exit
		while( counter_int++ < 5 ) {
			ThreadImpl::sleepForMSec(200);
			setThreadAlive();
			if ( this->sendNow ) {
				sendMessage();
				this->sendNow = false;
			 }
			if ( !this->running ) return;
		 }
		// about 1 second elapsed, eval counter vs. frequency
		// read mutex on freqNotify is not really required..
		counter_int = 0;
		if ( this->freqNotify == 0 ) continue;
		if ( counter++ < this->freqNotify ) continue;
		// time to send!
		counter = 0;
		sendMessage();
	 }
 }

void DiscoveryUdpImpl::exitingThread() {
	Log(LOG_DEBUG, ":exitingThread() UnBinding Socket.." );
	WRESULT unbinded = this->socket->unbindSocket();
	if ( WFAILED(unbinded) ) {
		Log(LOG_CRITICAL, ":exitingThread() FAILED#%d UNBinding socket", unbinded );
	 }
	WRESULT destroyed = ObjectFactory::destroy(this->socket);
	if ( WFAILED(destroyed) ) {
		Log(LOG_CRITICAL, ":exitingThread() FAILED#%d Destroying Socket", destroyed );
	 }
	Log(LOG_VERBOSE, ":exitingThread() Socket Destroyed." );
	this->socket = NULL;
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// OUTGOING MESSAGE

void DiscoveryUdpImpl::sendMessage() {
	// function is NOT reentrant against:  access this->msgBase,this->msgBuffer; call socket.sendDatagram()
	// function is reentrant against: this->broadcastAddressPort
	if ( this->socket == NULL ) {
		Log(LOG_CRITICAL, ":sendMessage() FAILED NULL socket (thread not running?)" );
		return;
	 }
	// retrieve last updated information about loca-host
	wosh::WoshHost* localHost = WoshKernel::getInstance()->network().getLocalWoshHost(); // it's a copy, I'll own it
	// update the outgoing discovery Message against the localHost object
	updateMessageByHost(this->msgBase, *localHost );
	delete localHost; localHost = NULL;
	// serialize the message to static buffer
	int size = this->msgBase.saveTo(this->msgBuffer, DiscoveryUdpMessage::MaxLength);
	if ( size <= 0 ) {
		// some internal error!? btw should never happen 
		Log(LOG_CRITICAL, ":sendMessage() FAILED Serializing Discovery Message!" );
		return;
	 }
	if ( size >= wosh::network::SocketUdp::MaxPacketLength ) {
		Log(LOG_WARNING, ":sendMessage() datagram Overflow [%ld > %ld]", size, wosh::network::SocketUdp::MaxPacketLength );
	 }
	// send the datagram to target hosts, access for reading to the current configuration
	this->settingsMux.lockForRead();
	std::vector<std::string>::const_iterator it;
	for ( it=this->broadcastAddressPort.begin(); it!=this->broadcastAddressPort.end(); it++ ) {
		// really send the discovery message, address format is IP:PORT (192.168.0.255:1564)
		int bs = this->socket->sendDatagram( *it, this->msgBuffer, size );
		if ( bs != size ) // something went wrong, not all bytes were sent
			Log(LOG_CRITICAL, ":sendMessage() FAILED sending to %s [size: %d, send: %d]", (*it).c_str(), size, bs );
		else // great, then move to next
			Log(LOG_DEBUG, ":sendMessage() Sent to %s [%d bytes]", (*it).c_str(), bs );
	 }
	this->settingsMux.unLock();
 }

//////////////////////////////////////////////////////////////////////////////////////////// OUTGOING MESSAGE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// UDP EVENTS

void DiscoveryUdpImpl::udpIncomingDatagram( const char * data, int maxSize, const std::string& source, wosh::network::SocketUdp* ) {
	// by default SocketUdp accepts also loopback datagrams, but depends on its configuration
	// for example: binding to x.y.z.K:p and sending data to x.y.z.255:p will cause a (single) loopback
	// it's very good for us, because we are almost ALWAYS in this situation
	// and so it will keep the local-host updated and consistent to what others host receive
	// in other words: ALL hosts may be updated from here, event local-host (just sent by myself)
	DiscoveryUdpMessage message;
	message.setSourceHost(source);
	// parse the datagram into temporary representation
	WRESULT loaded = message.loadFrom(data, maxSize);
	if ( WFAILED(loaded) ) {
		Log(LOG_WARNING, ":udpIncomingDatagram() INVALID#%d PACKET [%d] from %s", loaded, maxSize, source.c_str());
		return;
	 }
	// validate the remote host name, if the datagram was sent from another host
	// the kernel name MUST be different!
	// if ( message.getDiscoveryBindAddress() != this->socket.getAddressPort() ) {
	// 	if ( message.getKernelName() == wosh::WoshKernel::getKernelName() ) {
	// 		Log(LOG_WARNING, ":udpIncomingDatagram() Same Kernel-Name from %s [%s]!?", message.getKernelName().c_str(), source.c_str() );
	// 		return;
	// 	 }
	//  }
	Log(LOG_VERBOSE, ":udpIncomingDatagram() from %s [%s]", message.getKernelName().c_str(), source.c_str() );
	// update the temporary WoshHost object against the discovery message (just parsed)
	updateHostByMessage( this->currHost, message );
	// forward the updated information to NetworkManager
	WoshKernel::getInstance()->network().updateWoshHost( &this->currHost );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// UDP EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// UPDATE HOST/MESSAGE

void DiscoveryUdpImpl::updateHostByMessage( wosh::WoshHost& host, const DiscoveryUdpMessage& message ) {
	// update & overwrite fields of WoshHost
	// used while parsing incoming datagrams
	host.setSeen();
	host.setName( message.getKernelName() );
	host.setDiscoverSource( message.getSourceHost() );
	host.setDiscoveryBindAddress( message.getDiscoveryBindAddress() );
	host.setDiscoveryProtocolVersion( message.getProtocolVersion() );
	host.setNotifyFrequency( message.getNotifyFrequency() );
	host.setKernelState( message.getKernelState() );
	std::vector<std::string> prots;
	message.getSupportedProtocols(prots);
	host.setProtocols( prots );
	std::map<std::string, long> neighbours;
	message.getNeighbours(neighbours);
	host.setNeighbours( neighbours );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void DiscoveryUdpImpl::updateMessageByHost( DiscoveryUdpMessage& message, const wosh::WoshHost& host ) {
	// update & overwrite fields of DiscoveryUdpMessage
	// used when sending datagrams
	message.setKernelName( host.getName() );
	message.setKernelState( host.getKernelState() );
	message.setNotifyFrequency( host.getNotifyFrequency() );
	message.setSupportedProtocols( host.getProtocols() ); // only offered (not connected)
	message.setNeighbours( host.getNeighbours() ); // only offered (not connected)
 }

///////////////////////////////////////////////////////////////////////////////////////// UPDATE MESSAGE/HOST
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// START THREAD/BIND

WRESULT DiscoveryUdpImpl::sendDiscoveryUdpMessage( bool synch ) {
	if ( !isThreadRunning() ) {
		Log(LOG_CRITICAL, ":sendDiscoveryUdpMessage(%d) FAILED: not binded!", synch );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_VERBOSE, ":sendDiscoveryUdpMessage(%d) Sending Discovery (set flag)..", synch );
	this->sendNow = true;
	if ( !synch ) return WRET_OK;
	const int MAX = 15 * 10; // 15 seconds
	int i=0;
	while( ++i < MAX ) {
		if ( !this->sendNow ) {
			Log(LOG_INFO, ":sendDiscoveryUdpMessage() Sent Discovery (synch)" );
			return WRET_OK;
		 }
		ThreadImpl::sleepForMSec(100);
	 }
	Log(LOG_CRITICAL, ":sendDiscoveryUdpMessage() FAILED : Synch-Sending (timeout failed).." );
	return WRET_ERR_INTERNAL;
 }

/////////////////////////////////////////////////////////////////////////////////////////// START THREAD/BIND
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// REENTRANT GETTERS

std::string DiscoveryUdpImpl::getBindAddressPort() const {
	MutexLockerRead mL(&this->settingsMux);
	return this->bindAddressPort;
 }

std::vector<std::string> DiscoveryUdpImpl::getBroadcastAddressPort() const {
	MutexLockerRead mL(&this->settingsMux);
	return this->broadcastAddressPort;
 }

unsigned int DiscoveryUdpImpl::getNotifyFrequency() const {
	MutexLockerRead mL(&this->settingsMux);
	return this->freqNotify;
 }

/////////////////////////////////////////////////////////////////////////////////////////// REENTRANT GETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// REENTRANT SETTERS

void DiscoveryUdpImpl::setNotifyFrequency( unsigned int seconds ) {
	Log(LOG_VERBOSE, ":setNotifyFrequency(%ui)", seconds );
	MutexLockerWrite mL(&this->settingsMux);
	this->freqNotify = seconds;
	this->msgBase.setNotifyFrequency( seconds );
 }

WRESULT DiscoveryUdpImpl::setBindAddressPort( const std::string& address_port ) {
	if ( isThreadRunning() ) {
		Log(LOG_CRITICAL, ":setBindAddressPort(%s) : FAILED, Already binded!", address_port.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_VERBOSE, ":setBindAddressPort(%s)", address_port.c_str() );
	MutexLockerWrite mL(&this->settingsMux);
	this->bindAddressPort = address_port;
	this->msgBase.setDiscoveryBindAddress( address_port );
	return WRET_OK;
 }

int DiscoveryUdpImpl::setBroadcastAddressPort( const std::vector<std::string>& destinations ) {
	Log(LOG_VERBOSE, ":setBroadcastAddressPort() %d addresses", destinations.size() );
	MutexLockerWrite mL(&this->settingsMux);
	this->broadcastAddressPort.clear();
	int errors = 0;
	std::vector<std::string>::const_iterator it;
	for ( it=destinations.begin(); it!=destinations.end(); it++ ) {
		if ( wosh::network::NetworkUtilities::isAddressPortValid(*it) )
			this->broadcastAddressPort.push_back(*it);
		else
			++errors;
	 }
	if ( errors != 0 )
		return -errors;
	return this->broadcastAddressPort.size();
 }

int DiscoveryUdpImpl::setBroadcastAddressPort( const std::string& addresses ) {
	std::vector<std::string> destinations;
	wosh::Utilities::splitString(addresses, ";", destinations, false, true );
	return setBroadcastAddressPort(destinations);
 }

/////////////////////////////////////////////////////////////////////////////////////////// REENTRANT SETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
